package wiki.parser
{
	import mx.collections.ArrayCollection;
	
	/**
	 * An instance of this class can parse texts that are in MediaWiki like format to HTML
	 * It uses regular expressions to convert a Wiki string to HTML.
	 * 
	 */ 
	public class WikiParser {
		
		/**
		 * Constructs a new WikiParser instance
		 */
		public function WikiParser() {
			_patterns = new ArrayCollection();
			addMediaWikiPatterns();
		}
		
		public function addPattern(wp:WikiPattern): void {
			if (wp!= null) {
				_patterns.addItem(wp);
			}
		}
		
		public function set patterns(newPatterns: ArrayCollection): void {
			_patterns = newPatterns;
		}
		
		/**
		 * Converts a wiki text (input) to HTML text.
		 */
		public function parse(input:String):String {
			// convert all returns (\r) to newlines (\n)
			var result:String = input.replace(/\r/gm, "\n"); 
			
			// apply all the patterns 
			for (var i:int; i<_patterns.length; i++) {
				var wp: WikiPattern = _patterns[i] as WikiPattern;
				result = wp.parse(result);
			}
			
			// find all paragraphs and lists and generate the apporpriate HTML
			result = linesToParagraphs(parseLists(result));
			trace("parse result:\n" + result);
			return result;
		}
		
		/**
		 * creates the patterns for parsing wiki content
		 * TODO: move these to a separate class / component
		 */
		private function addMediaWikiPatterns(): void {
			
			// heading level3 pattern: ====text==== becomes <h3>text</h3>
			addPattern(new WikiPattern(/====(.*?)====/g, "<h3>$1</h3>"));
			
			// heading level2 pattern: ===text=== becomes <h2>text</h2>
			addPattern(new WikiPattern(/===(.*?)===/g, "<h2>$1</h2>"));
			
			// heading level1 pattern: ==text== becomes <h1>text</h1>
			addPattern(new WikiPattern(/==(.*?)==/g, "<h1>$1</h1>"));
			
			// bold pattern: '''text''' becomes <b>text</b>
			addPattern(new WikiPattern(/'''(.*?)'''/g, "<b>$1</b>"));
			
			// italics pattern: ''text'' becomes <i>text</i>
			addPattern(new WikiPattern(/''(.*?)''/g, "<i>$1</i>"));	
			
			// horizontal line pattern: --- becomes <hr/>
			addPattern(new WikiPattern(/\-\-\-/g, "<hr/>"));	
			
			// image: 
			// [[http://www.example.com/image.gif]] becomes <img src='www.example.com/image.gif'/>
			addPattern(new WikiPattern(/\[\[(.*)\.(gif|jpg|png|jpeg)\]\]/gm, "<img src='$1.$2'/>"));
			
			// external link pattern: 
			// [[http://www.example.com]] becomes <a href='www.example.com'>www.example.com</a>
			addPattern(new WikiPattern(/\[\[((?:http|ftp):\/\/)(.*)\]\]/gm, "<a href='$1$2'>$1$2</a>"));

			// internal link pattern: [[page]] becomes <a href='#' id='ilink'>page</a>
			addPattern(new WikiPattern(/\[\[(.*)\]\]/gm, "<a href='#' id='ilink'>$1</a>"));
			
		}
		
		/**
		 * Returns the type of the first list item contained in the input (wiki syntax).
		 * If the first list item starts with '*', then "ul" is returned.
		 * Otherwise it must be a numbered list item, so "ol" is returned.
		 */
		private function getListType(input:String):String {
			var matches:Array = input.match(PATTERN_BULLET);
			return (matches!=null && matches.length>0) ? "ul" : "ol";
		}
		
		/**
		 * Helper function for parseLists that recursively replaces the next lower level list inside the first argument.
		 * The second argument that is passed to parseListItems should contain a complete item list.
		 */
		private function parseListItems():String {
			// get the item list from the second argument
            var result:String = arguments[1];
			var type:String = getListType(result);
            // remove the * or # of the top-level list items
            result = result.replace(PATTERN_LISTITEMTAG, "$1");
            // recursively parse the remaining list items
            result = parseLists(result);
            // wrap the result in HTML list tags
			result = "<" + type + "><li>" + result.replace(/^\n/, "").split(/\n/).join("</li><li>") + "</li></" + type + ">";
			return result;
		}
		
		/**
		 * Replaces multi-level bullet lists and numbered lists with HTML bullet lists and HTML numbered lists.
		 * It uses a helper function for recursively traversing lists that have multiple levels.
		 */
		private function parseLists(input:String):String {
			// for each occurance of the PATTERN_LIST RegExp in the input string, call the parseListItems function
			// The second argument that is passed to parseListItems contains a complete item list
			var result:String = input.replace(PATTERN_LIST, parseListItems); 
			return result;
		}
		
		/**
		 * Replaces a newline in a wiki string with the 
		 * <p> HTML tag. 
		 */
		private function linesToParagraphs(input:String):String {
			
			// remove empty lines first
			var pattern:RegExp = /^$/gm;
			var result:String = input.replace(pattern, "");
			
			// Add <p> tags around all remaining lines, but not those that start with * (asterix) because these are bullet items
			pattern = /^([^*^#].*)$/gm;
			return result.replace(pattern, "<p>$1</p>");
		}	
		
		private var _patterns: ArrayCollection; //ordered collection of WikiPattern instances
		
		/** 
		 * RegExp for matching the start of a bullet list item ("*") 
		 */
		private static const PATTERN_BULLET:RegExp = /(^|\n)\*/g;
		
		/** 
		 * RegExp for matching a bulleted list item tag ('*' or '* ') or numbered list item tag ('#' or '# ') 
		 */ 
		private static const PATTERN_LISTITEMTAG:RegExp = /(^|\n)[\*#][ ]{0,1}/g;
		
		/** 
		 * RegExp for matching a complete (multiline) list 
		 * This could be a mix of bullet list items and numbered list items
		 */
		private static const PATTERN_LIST:RegExp = /(((^|\n)[\*#].*)+)/gm;
			
	}
}